// 12.1 动态内存与智能指针
/**
 * 在C++中，动态内存的管理是通过一对运算符来完成的：
 * 1. new，在动态内存中为对象分配空间并返回一个指向该对象的指针，我们可以选择对对象进行初始化；
 * 2. delete，接受一个动态对象的指针，销毁该对象，并释放与之关联的内存。
 * 新标准库提供的这两种智能指针的区别在于管理底层指针的方式：
 * 1. shared_ptr允许多个指针指向同一个对象；
 * 2. unique_ptr则“独占”所指向的对象。
 * 标准库还定义了一个名为weak_ptr的伴随类，它是一种弱引用，指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;

// 在函数被调用时ptr被创建并初始化
void process(shared_ptr<int> ptr)
{
    // 使用ptr
} // ptr离开作用域，被销毁
// process的参数是传值方式传递的，因此实参会被拷贝到ptr中。拷贝一个shared_ptr会递增其引用计数，
// 因此，在process运行过程中，引用计数值至少为2。当process结束时，ptr的引用计数会递减，但不会变为0。
// 因此，当局部变量ptr被销毁时，ptr指向的内存不会被释放。

int main()
{
    // 12.1.3 shared_ptr和new结合使用
    // 如果我们不初始化一个智能指针，它就会被初始化为一个空指针。
    shared_ptr<double> p1;           // shared_ptr可以指向一个double
    shared_ptr<int> p2(new int(42)); // p2指向一个值为42的int
    // 接受指针参数的智能指针构造函数是explicit的（参见7.5.4节，第265页）。
    // 因此，我们不能将一个内置指针隐式转换为一个智能指针，必须使用直接初始化形式（参见3.2.1节，第76页）来初始化一个智能指针：[插图]
    // shared_ptr<int> p3 = new int(1024); // 错误，必须使用直接初始化形式，我们不能进行内置指针到智能指针间的隐式转换
    shared_ptr<int> p4(new int(1024));
    // 定义和改变shared_ptr的其他方法
    // 1. shared_ptr<T> p(q) p管理内置指针q所指向的对象：q必须指向new分配的内存，且能够转换成T*类型
    // 2. shared_ptr<T> p(u) p从unique_ptr u那里接管了对象的所有权：将u置空
    // 3. shared_ptr<T> p(q,d) p接管了内置指针q所指向的对象的所有权。q必须能转换为T*类型。p将使用可调用对象d来代替delete。
    // 4. shared_ptr<T> p(p2,d) p是shared_ptr p2的拷贝，p将使用可调用对象d来代替delete。
    // 5. p.reset() 若p是唯一指向其对象的shared_ptr，reset会释放此对象。
    // 6. p.reset(q) 若传递了可选的参数内置指针q，会令p指向q，否则将p置空。
    // 7. p.reset(q,d) 若还传递了参数d，将会调用d而不是delete来释放q。

    // 不要混合使用普通指针和智能指针
    // shared_ptr可以协调对象的析构，但这仅限于其自身的拷贝（也是shared_ptr）之间。这也是为什么我们推荐使用make_shared而不是new的原因。
    // 这样，我们就能在分配对象的同时就将shared_ptr与之绑定，从而避免了无意中将同一块内存绑定到多个独立创建的shared_ptr上。
    int *x(new int(1024)); // 危险，x是个普通指针，不是一个智能指针
    // process(x); // 错误，不能将int*转换成shared_ptr<int>
    process(shared_ptr<int>(x)); // 合法的，但内存会被释放
    int j = *x;                  // 未定义的，x现在变成一个空悬指针
    // 使用一个内置指针来访问一个智能指针所负责的对象是很危险的，因为我们无法知道对象何时会被销毁。

    // 也不要使用get初始化另一个智能指针或为智能指针赋值
    // 智能指针类型定义了一个名为get的函数（参见表12.1），它返回一个内置指针，指向智能指针管理的对象。
    // 此函数是为了这样一种情况而设计的：我们需要向不能使用智能指针的代码传递一个内置指针。
    // 使用get返回的指针的代码不能delete此指针。
    shared_ptr<int> p(new int(1024)); // 引用计数为1
    int *q = p.get();                 // 正确，但使用q时要注意，不要让它管理的指针被释放
    {                                 // 新程序块
        shared_ptr<int>(q);           // 未定义，两个独立的shared_ptr指向相同的内存
    }                                 // 程序块结束，q被销毁，它指向的内存被释放
    int foo = *p;                     // 未定义，p指向的内存已经释放了
    // get用来将指针的访问权限传递给代码，你只有在确定代码不会delete指针的情况下，才能使用get。
    // 特别是，永远不要用get初始化另一个智能指针或者为另一个智能指针赋值。

    // 其他shared_ptr操作
    // shared_ptr还定义了其他一些操作，参见表12.2和表12.3所示。我们可以用reset来将一个新的指针赋予一个shared_ptr：[插图]
    // p = new int(1024); // 错误，不能将一个指针赋予shared_ptr
    p.reset(new int(1024)); // 正确，p指向一个新对象
    // reset成员经常与unique一起使用，来控制多个shared_ptr共享的对象。
    if (!p.unique())
    {
        p.reset(new int(*p)); // 我们不是唯一用户，分配新的拷贝
    }
    *p += 1; // 现在我们知道自己是唯一的用户，可以改变对象的值

    return 0;
}